package com.mall.seckill_mall.service.impl;

import com.mall.seckill_mall.common.RedisPreffix;
import com.mall.seckill_mall.common.SeckillMessage;
import com.mall.seckill_mall.common.SeckillStatus;
import com.mall.seckill_mall.entity.Goods;
import com.mall.seckill_mall.entity.Order;
import com.mall.seckill_mall.entity.User;
import com.mall.seckill_mall.enums.CodeMessage;
import com.mall.seckill_mall.enums.SeckillCode;
import com.mall.seckill_mall.exception.GoodsException;
import com.mall.seckill_mall.exception.SeckillException;
import com.mall.seckill_mall.exception.UserException;
import com.mall.seckill_mall.rabbitmq.sender.MQProducer;
import com.mall.seckill_mall.service.GoodsService;
import com.mall.seckill_mall.service.OrderService;
import com.mall.seckill_mall.service.SeckillService;
import com.mall.seckill_mall.service.UserService;
import org.omg.CORBA.ORB;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    @Autowired
    private OrderService orderService;

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private UserService userService;

    @Autowired
    private MQProducer mqProducer;

    @Override
    public String generatePath(String userToken, String goodsId) {
        goodsService.checkSeckillStatus(goodsId);
        String pathKey = getPathKey(userToken, goodsId);
        String path = UUID.randomUUID().toString().replaceAll("-", "");
        redisTemplate.opsForValue().set(pathKey, path, 1, TimeUnit.DAYS);
        return path;
    }

    @Override
    public boolean validatePath(String userToken, String goodsId, String path) {
        String pathKey = getPathKey(userToken, goodsId);
        String cachedPath = (String) redisTemplate.opsForValue().get(pathKey);
        if(cachedPath == null) {
            throw new SeckillException(CodeMessage.INVALID_REQUEST);
        }
        if(path.equals(cachedPath)) {
            redisTemplate.delete(pathKey);
            return true;
        }
        return false;
    }

    private void validateAuth(String userId, String goodsId) {
        //1. 查是否正在秒杀
        goodsService.checkSeckillStatus(goodsId);
        //2. 查是否有订单
        String orderId = userId.concat("_").concat(goodsId);
        Order order = orderService.getOrderByOrderId(orderId);
        if(order != null) {
            throw new SeckillException(CodeMessage.REPEAT_SECKILL);
        }
        //3. 查缓存库存
        String stockKey = RedisPreffix.STOCK_PREFFIX.concat(goodsId);
        int stock = (int) redisTemplate.opsForValue().get(stockKey);
        if(stock <= 0) {
            throw new SeckillException(CodeMessage.STOCK_OVER);
        }
    }

    @Override
    public void doSeckill(String userToken, String goodsId) {
        Goods goods = goodsService.getGoods(goodsId);
        User user = userService.getUserByToken(userToken);
        if(user == null) {
            throw new SeckillException(CodeMessage.PLEASE_LOGIN);
        }
        if(goods == null) {
            throw new SeckillException(CodeMessage.GOODS_NOT_EXISTS);
        }
        validateAuth(user.getUserId(), goodsId);
        SeckillMessage seckillMessage = new SeckillMessage();
        seckillMessage.setUserId(user.getUserId());
        seckillMessage.setGoods(goods);
        mqProducer.sendSeckill(seckillMessage, 60 * 1000);
    }

    @Override
    //此处可增加分布式锁，重试次数为5
    //不用锁，因为是从MQ中拉取订单请求，所以串行的不存在线程安全
    @Transactional
    public void seckill(String userId, Goods goods) {
        validateAuth(userId, goods.getGoodsId());
        //1. 预减库存
        Long currentStock = redisTemplate.opsForValue().decrement(RedisPreffix.STOCK_PREFFIX.concat(goods.getGoodsId()));
        if(currentStock == null || currentStock < -1L) {
            throw new SeckillException(CodeMessage.STOCK_OVER);
        }
        //2. db增加锁定库存
        goodsService.preReduceStock(goods.getGoodsId());
        //3. 生成订单
        String orderId = orderService.createOrder(userId, goods);
        //4. 将订单放入超时队列
        mqProducer.sendOrder(orderId, 60 * 1000);
    }

    @Override
    public SeckillCode getSeckillResult(String userToken, String goodsId) {
        Goods goods = (Goods) redisTemplate.opsForValue().get(RedisPreffix.GOODS_PREFFIX.concat(goodsId));
        User user = (User) redisTemplate.opsForValue().get(RedisPreffix.ONLINE_USER_PREFFIX.concat(userToken));
        if(user == null) {
            throw new UserException(CodeMessage.PLEASE_LOGIN);
        }
        /* 2. 查看redis用户秒杀失败标记，若失败，返回秒杀失败 */
        Boolean isFailed = (Boolean) redisTemplate.opsForValue()
                .get(RedisPreffix.SECKILL_FALIED_PREFFIX.concat(user.getUserId()).concat(("_").concat(goodsId)));
        if(isFailed != null && isFailed) {
            return SeckillCode.SECKILL_FAILED;
        }
        /* 3. 查看redis中是否存在订单，若存在，返回秒杀成功 */
        String orderKey = RedisPreffix.ORDER_PREFFIX.concat(user.getUserId()).concat("_").concat(goodsId);
        Order order = (Order) redisTemplate.opsForValue().get(orderKey);
        if(order != null) {
            return SeckillCode.SECKILL_SUCCESS;
        }
        /* 4. 查看商品库存，若不够，返回秒杀失败 */
        int stock = (int) redisTemplate.opsForValue().get(RedisPreffix.STOCK_PREFFIX.concat(goodsId));
        if(stock <= 0) {
            return SeckillCode.SECKILL_FAILED;
        }
        /* 5. 都不是，返回秒杀排队中 */
        return SeckillCode.SECKILL_QUEUED;
    }

    @Override
    @Transactional
    public void closeSeckill(String orderId) {
        Order order = orderService.getOrderByOrderId(orderId);
        if(order != null) {
            orderService.deleteOrder(orderId);
            goodsService.releaseStock(order.getGoodsId());
            goodsService.addGoodsStock(order.getGoodsId(), 1);
        }
    }

    @Override
    @Transactional
    public void payForSeckill(String orderId, String userToken) {
        Order order = orderService.getOrderByOrderId(orderId);
        if(order == null) {
            throw new SeckillException(CodeMessage.ORDER_TIMEOUT);
        }
        User user = userService.getUserByToken(userToken);
        if(user == null) {
            throw new UserException(CodeMessage.PLEASE_LOGIN);
        }
        user = userService.getUser(user.getUserId());
        int goodsPrice = parseYuan(order.getOrderPrice());
        int userBalance = parseYuan(user.getUserBalance());
        int remain = userBalance - goodsPrice;
        if(remain < 0) {
            throw new SeckillException(CodeMessage.BALANCE_NOT_ENOUGH);
        }
        userService.updateBalance(user.getUserId(), parseFen(remain));
        orderService.updateOrderStatus(orderId);
        goodsService.reduceStock(order.getGoodsId());
    }

    @Override
    public BufferedImage createVerifyCode(String goodsId, String userToken) {
        User user = userService.getUserByToken(userToken);
        if(user == null) {
            throw new SeckillException(CodeMessage.PLEASE_LOGIN);
        }
        int width = 80;
        int height = 32;
        //create the image
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        // set the background color
        g.setColor(new Color(0xDCDCDC));
        g.fillRect(0, 0, width, height);
        // draw the border
        g.setColor(Color.black);
        g.drawRect(0, 0, width - 1, height - 1);
        // create a random instance to generate the codes
        Random rdm = new Random();
        // make some confusion
        for (int i = 0; i < 50; i++) {
            int x = rdm.nextInt(width);
            int y = rdm.nextInt(height);
            g.drawOval(x, y, 0, 0);
        }
        // generate a random code
        String verifyCode = generateVerifyCode(rdm);
        g.setColor(new Color(0, 100, 0));
        g.setFont(new Font("Candara", Font.BOLD, 24));
        g.drawString(verifyCode, 8, 24);
        g.dispose();
        //把验证码存到redis中
        int rnd = calc(verifyCode);
        String redisKey = RedisPreffix.VERIFY_CODE_PREFFIX.concat(user.getUserId()).concat("_").concat(goodsId);
        redisTemplate.opsForValue().set(redisKey, rnd, 30, TimeUnit.SECONDS);
        //输出图片
        return image;
    }

    @Override
    public boolean checkVerify(String goodsId, String userToken, int code) {
        User user = userService.getUserByToken(userToken);
        if(user == null) {
            throw new SeckillException(CodeMessage.PLEASE_LOGIN);
        }
        String redisKey = RedisPreffix.VERIFY_CODE_PREFFIX.concat(user.getUserId()).concat("_").concat(goodsId);
        Integer cachedCode = (Integer) redisTemplate.opsForValue().get(redisKey);
        if(cachedCode == null) {
            throw new SeckillException(CodeMessage.SERVER_ERROR);
        }
        System.out.println("in_code " + code + ", cache " + cachedCode);
        redisTemplate.delete(redisKey);
        return code == cachedCode;
    }

    private static int calc(String exp) {
        try {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("JavaScript");
            return (Integer)engine.eval(exp);
        }catch(Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    private static char[] ops = new char[] {'+', '-', '*'};
    /**
     * + - *
     * */
    private String generateVerifyCode(Random rdm) {
        int num1 = rdm.nextInt(10);
        int num2 = rdm.nextInt(10);
        int num3 = rdm.nextInt(10);
        char op1 = ops[rdm.nextInt(3)];
        char op2 = ops[rdm.nextInt(3)];
        String exp = ""+ num1 + op1 + num2 + op2 + num3;
        return exp;
    }

    private String getPathKey(String userToken, String goodsId) {
        User user = (User) redisTemplate.opsForValue().get(RedisPreffix.ONLINE_USER_PREFFIX.concat(userToken));
        if(user == null) {
            throw new UserException(CodeMessage.PLEASE_LOGIN);
        }
        return RedisPreffix.PATH_PREFFIX.concat(user.getUserId()).concat("_").concat(goodsId);
    }

    private int parseYuan(double yuan) {
        return (int) (yuan * 100);
    }

    private double parseFen(int fen) {
        return new BigDecimal(fen).divide(new BigDecimal(100)).setScale(2).doubleValue();
    }
}
